/* mm/memory.c
 * liuw@liuw.name
 * 31 Jul, 2008
 */
/*
   there is a unsigned char array which maps the whole phys memory
   we use this array to tell the state of certain page

   Functions:
   invalidate()			flush CR3
   copy_page()		
   get_page()			get a free page
   free_page()			free a used page
   put_page()			map page to addr
   do_no_page()			called by do_page_fault when present==0
   do_page_fault()		interrupt handler
   mem_info()			aux func, print memory info
   init_mm()			init mem_map[] according to macros

 */
#include <init/isr.h>

unsigned long HIGH_MEMORY = 0;

// flush CR3 
#define invalidate()					\
	asm volatile("movl %%eax, %%cr3"::"a"(0))

// NOTE: don't change the macros below
// reserved 4MB ram for kernel
#define LOW_MEMORY 		0x100000 //kernel is loaded here
#define PAGESIZE_ORDER 		12
#define PAGESIZE 		4096
#define SYS_RESERVED_MEM 	(4*1024*1024)
#define PAGING_MEMORY 		(31*1024*1024)
#define NR_PAGES 		(PAGING_MEMORY>>PAGESIZE_ORDER)
#define USED 			100

#define copy_page(from, to)						\
	asm volatile("cld; rep; movsl"::"S"(from),"D"(to),"c"(1024):"cx", "di", "si")

static unsigned char mem_map[NR_PAGES] = {0, };

unsigned long get_page(void)
{
	unsigned long res;
	int i;
	for (i = LOW_MEMORY >> PAGESIZE_ORDER; i < NR_PAGES; ++i) {
		if (mem_map[i] == 0) {
			++mem_map[i];
			res = i << PAGESIZE_ORDER;
			memset(res, 0, PAGESIZE);
			printk("get_page: %08x\n", res);
			return res;
		}
	}
	return 0;
}

void free_page(unsigned long addr)
{
	if (addr < LOW_MEMORY) return;
	if (addr > HIGH_MEMORY)
		crash("trying to free nonexistent page");

	addr >>= 12;

	if (mem_map[addr]--) return;
	mem_map[addr] = 0;
	crash("trying to free free page");
}

// this has not been fully tested
int copy_page_tables(unsigned long from, unsigned long to, long size)
{
	unsigned long *from_pg_table;
	unsigned long *to_pg_table;
	unsigned long this_page;
	unsigned long *from_dir, *to_dir;
	int nr;

	// MUST be 4MB aligned
	if ((from & 0x3fFFFF) || (to & 0x3fFFFF))
		crash("copy_page_tables called with wrong alignment");
	from_dir = (unsigned long *)((from >> 20) & 0xffc);
	to_dir = (unsigned long *)((to >> 20) & 0xffc);
	// size = Page Directory needed
	size = ((unsigned)(size + 0x3fFFFF)) >> 22;

	for (; size-- > 0; from_dir++, to_dir++) {
		if (*to_dir & 1) // present?
			crash("copy_page_tables: already exists");
		if (!(*from_dir & 1)) // not present?
			continue;

		from_pg_table = (unsigned long *)(0xffffF000 & *from_dir);
		if (!(to_pg_table = (unsigned long *)get_page()))
			return -1;
		*to_dir = ((unsigned long) to_pg_table) | 7;
		nr = 1024;
		for (; nr-- > 0; from_pg_table++, to_pg_table++) {
			this_page = *from_pg_table;
			if (!(this_page & 1))
				continue;
			this_page &= ~2;
			*to_pg_table = this_page;
			if (this_page > SYS_RESERVED_MEM) {
				*from_pg_table = this_page;
				this_page >>= 12;
				mem_map[this_page]++;
			}
		}
	}
	invalidate();
	return 0;
}


// this has not been fully tested
int free_page_tables(unsigned long from, unsigned long size)
{
	unsigned long *pg_table;
	unsigned long *dir, nr;

	if (from & 0x3fFFFF)
		crash("free_page_tables called with wrong alignment");
	if (!from)
		crash("trying to free up swapper memory space");

	// PageDirBase starts from phys addr 0!
	// if it is somewhere else, add offset
	size = (size + 0x3fFFFF) >> 22;
	dir = (unsigned long *) ((from >> 20) & 0xffc);

	for (; size-- > 0; dir++) {
		if (!(1 & *dir))
			continue;
		pg_table = (unsigned long *)(0xffffF000 & *dir);
		for (nr = 0; nr < 1024; nr++) {
			if (1 & *pg_table)
				free_page(0xffffF000 & *pg_table);
			*pg_table = 0;
			pg_table++;
		}
		free_page(0xffffF000 & *dir);
		*dir = 0;
	}
	invalidate();
	return 0;
}

unsigned long put_page(unsigned long page, unsigned long addr)
{
	unsigned long tmp, *pg_table;

	if (page < LOW_MEMORY || page > HIGH_MEMORY)
		printk("Trying to put page %08x at %08x\n", page, addr);
	if (mem_map[(page)>>12] != 1) {
		printk("mem_map disagree with %08x at %08x\n", page, addr);
		printk("mem_map[%08x] = %d\n", (page)>>12, 
		       mem_map[(page)>>12]);
	}


	// Remember PageDirBase = 0 
	pg_table = (unsigned long *) ((addr >> 20) & 0xffc);
	if ((*pg_table) & 1) {// it is present, good!
		pg_table = (unsigned long *)(0xffffF000 & *pg_table);
		printk("put_page: present %08x\n", pg_table);
	}

	else { // oops, not present, allocat a new page
		if (!(tmp = get_page()))
			return 0;
		*pg_table = tmp | 7; // set rw user present
		pg_table = (unsigned long *) tmp;
		printk("put_page: pg_table not present, get %08x\n", pg_table);
	}
	pg_table[(addr >> 12) & 0x3ff] = page | 7;
	return page;
}

int do_no_page(unsigned long addr)
{
	unsigned long p = get_page();
	printk("do_no_page: %08x mapping to %08x\n", p, addr );
	put_page(p, addr);
	return 0;
}

void do_page_fault(registers_t r)
{
	unsigned long faultaddr;
	asm volatile("movl %%cr2, %0":"=r"(faultaddr));
	printk("Page Fault address: %08x ", faultaddr);
	if (!(r.err_code & 0x1))
		printk("present ");
	if (r.err_code & 0x2)
		printk("read-only ");
	if (r.err_code & 0x4)
		printk("user-mode ");
	if (r.err_code & 0x8)
		printk("reserved ");
	printk("\n");

	if (!(r.err_code & 0x1))
		do_no_page(faultaddr);
}

void mem_info(void)
{
	int used = 0;
	int i;
	printk("There are %d pages\n", NR_PAGES);
	for (i = 0; i < NR_PAGES; ++i) {
		if (mem_map[i] == USED)
			used++;
	}
	printk("used pages: %d\n", used);
}


// initialize mem_map[]
void init_mm(void)
{
	int i;

	for (i = 0; i < NR_PAGES; ++i)
		mem_map[i] = USED;

	i = SYS_RESERVED_MEM >> 12;

	for (; i < NR_PAGES; ++i)
		mem_map[i] = 0;

}
